// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2020, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * Lombo n7 DMA controller register operations
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <linux/types.h>
#include <linux/dmaengine.h>

#include "dma_operate.h"

#define DMA_ENABLE_OFFSET	0x00000010
#define DMA_ABORT_OFFSET	0x00000014
#define DMA_HALT_OFFSET		0x00000018
#define DMA_COMB_INT_PD_OFFSET	0x0000001C
#define DMA_CHANNEL_OFFSET	0x00000100
#define DMA_CFG0_OFFSET		0x00000100
#define DMA_CFG1_OFFSET		0x00000104
#define DMA_LLP_OFFSET		0x00000108
#define DMA_INT_EN_OFFSET	0x00000160
#define DMA_INT_PD_OFFSET	0x00000164
#define DMA_INT_CLR_OFFSET	0x00000168

/**
 * csp_dma_set_channel_enable - enable or disable special channel
 */
void csp_dma_set_channel_enable(u32 index, u32 value, void *base)
{
	void *reg = base + DMA_ENABLE_OFFSET;
	reg_dma_enable_t dma_en_reg;

	dma_en_reg.val = readl(reg);
	switch (index) {
	case 0:
		dma_en_reg.bits.ch0_en = value;
		dma_en_reg.bits.ch0_en_we = value;
		break;
	case 1:
		dma_en_reg.bits.ch1_en = value;
		dma_en_reg.bits.ch1_en_we = value;
		break;
	case 2:
		dma_en_reg.bits.ch2_en = value;
		dma_en_reg.bits.ch2_en_we = value;
		break;
	case 3:
		dma_en_reg.bits.ch3_en = value;
		dma_en_reg.bits.ch3_en_we = value;
		break;
	default:
		return;
	}
	writel(dma_en_reg.val, reg);
}

/**
 * csp_dma_set_channel_abort - enable or disable special channel
 */
void csp_dma_set_channel_abort(u32 index, u32 value, void *base)
{
	void *reg = base + DMA_ABORT_OFFSET;
	reg_dma_abort_t dma_abort_reg;
	reg_dma_enable_t dma_en_reg;

	dma_abort_reg.val = readl(reg);
	switch (index) {
	case 0:
		dma_abort_reg.bits.ch0_abort = value;
		dma_abort_reg.bits.ch0_abort_we = 1;
		break;
	case 1:
		dma_abort_reg.bits.ch1_abort = value;
		dma_abort_reg.bits.ch1_abort_we = 1;
		break;
	case 2:
		dma_abort_reg.bits.ch2_abort = value;
		dma_abort_reg.bits.ch2_abort_we = 1;
		break;
	case 3:
		dma_abort_reg.bits.ch3_abort = value;
		dma_abort_reg.bits.ch3_abort_we = 1;
		break;
	default:
		return;
	}

	writel(dma_abort_reg.val, reg);

	reg = base + DMA_ENABLE_OFFSET;
	while (1) {
		dma_en_reg.val = readl(reg);
		if ((dma_en_reg.val & (1 << index)) == 0)
			break;
	}
}

/**
 * csp_dma_halt_channel_trans - halt block chain lli transfer
 */
void csp_dma_halt_channel_trans(u32 index, void *base)
{
	void *reg = base + DMA_HALT_OFFSET;
	reg_dma_halt_t reg_dma_halt;

	reg_dma_halt.val = readl(reg);
	switch (index) {
	case 0:
		reg_dma_halt.bits.ch0_halt = DMA_HALT_CH0_HALT_1;
		reg_dma_halt.bits.ch0_halt_we = DMA_HALT_CH0_HALT_1;
		break;
	case 1:
		reg_dma_halt.bits.ch1_halt = DMA_HALT_CH0_HALT_1;
		reg_dma_halt.bits.ch1_halt_we = DMA_HALT_CH0_HALT_1;
		break;
	case 2:
		reg_dma_halt.bits.ch2_halt = DMA_HALT_CH0_HALT_1;
		reg_dma_halt.bits.ch2_halt_we = DMA_HALT_CH0_HALT_1;
		break;
	case 3:
		reg_dma_halt.bits.ch3_halt = DMA_HALT_CH0_HALT_1;
		reg_dma_halt.bits.ch3_halt_we = DMA_HALT_CH0_HALT_1;
		break;
	default:
		return;
	}

	writel(reg_dma_halt.val, reg);
}

/**
 * csp_dma_resume_channel_trans - resume block chain lli transfer
 */
void csp_dma_resume_channel_trans(u32 index, void *base)
{
	void *reg = base + DMA_HALT_OFFSET;
	reg_dma_halt_t reg_dma_halt;

	reg_dma_halt.val = readl(reg);
	switch (index) {
	case 0:
		reg_dma_halt.bits.ch0_halt = DMA_HALT_CH0_HALT_0;
		reg_dma_halt.bits.ch0_halt_we = DMA_HALT_CH0_HALT_0;
		break;
	case 1:
		reg_dma_halt.bits.ch1_halt = DMA_HALT_CH0_HALT_0;
		reg_dma_halt.bits.ch1_halt_we = DMA_HALT_CH0_HALT_0;
		break;
	case 2:
		reg_dma_halt.bits.ch2_halt = DMA_HALT_CH0_HALT_0;
		reg_dma_halt.bits.ch2_halt_we = DMA_HALT_CH0_HALT_0;
		break;
	case 3:
		reg_dma_halt.bits.ch3_halt = DMA_HALT_CH0_HALT_0;
		reg_dma_halt.bits.ch3_halt_we = DMA_HALT_CH0_HALT_0;
		break;
	default:
		return;
	}

	writel(reg_dma_halt.val, reg);
}

/**
 * csp_dma_set_channel_cfg - set channel config
 */
void csp_dma_set_channel_cfg(u32 index, u32 direction, u32 priority,
				    u32 slave_id, void *base)
{
	void *cfg0_reg = base + index * DMA_CHANNEL_OFFSET + DMA_CFG0_OFFSET;
	void *cfg1_reg = base + index * DMA_CHANNEL_OFFSET + DMA_CFG1_OFFSET;
	reg_dma_ch0_cfg0_t dma_cfg0_reg;
	reg_dma_ch0_cfg1_t dma_cfg1_reg;

	dma_cfg0_reg.val = readl(cfg0_reg);
	dma_cfg1_reg.val = readl(cfg1_reg);

	dma_cfg0_reg.bits.trans_type = CH_CHAINS_TRANS;
	if (direction == DMA_MEM_TO_MEM) {
		dma_cfg0_reg.bits.srp = MEM_RT_RP;
		dma_cfg0_reg.bits.drp = MEM_RT_RP;
	} else if (direction == DMA_MEM_TO_DEV) {
		dma_cfg0_reg.bits.srp = MEM_RT_RP;
		dma_cfg0_reg.bits.drp = slave_id;
	} else if (direction == DMA_DEV_TO_MEM) {
		dma_cfg0_reg.bits.drp = MEM_RT_RP;
		dma_cfg0_reg.bits.srp = slave_id;
	}
	dma_cfg0_reg.bits.trans_dir = direction;

	dma_cfg1_reg.bits.prio = priority;


	writel(dma_cfg0_reg.val, cfg0_reg);
	writel(dma_cfg1_reg.val, cfg1_reg);
}

/**
 * csp_dma_org_channel_ctrl - orginate channel control register value
 */
u32 csp_dma_org_channel_ctrl(u32 sag, u32 dag, u32 swidth, u32 dwidth,
				      u32 src_blen, u32 dst_blen)
{
	reg_dma_ch0_ctl_t dma_ctrl_reg;

	dma_ctrl_reg.val = 0;
	dma_ctrl_reg.bits.src_ag = sag;
	dma_ctrl_reg.bits.dst_ag = dag;
	dma_ctrl_reg.bits.src_width = swidth;
	dma_ctrl_reg.bits.dst_width = dwidth;
	dma_ctrl_reg.bits.src_blen = src_blen;
	dma_ctrl_reg.bits.dst_blen = dst_blen;
	dma_ctrl_reg.bits.lli_last = CH_LLI_NO_LAST;
	dma_ctrl_reg.bits.lli_ie = CH_LLI_INT_ENABLE;
	dma_ctrl_reg.bits.lli_valid = CH_LLI_VALID;

	return dma_ctrl_reg.val;
}

/**
 * csp_dma_set_channel_lli_last - set lli last to true
 */
void csp_dma_set_channel_lli_last(u32 *value, bool is_last)
{
	reg_dma_ch0_ctl_t *ch_ctrl_reg = (reg_dma_ch0_ctl_t *)value;

	if (is_last)
		ch_ctrl_reg->bits.lli_last = CH_LLI_LAST;
	else
		ch_ctrl_reg->bits.lli_last = CH_LLI_NO_LAST;
}

/**
 * csp_dma_set_channel_llp - set first chain llp address
 */
void csp_dma_set_channel_llp(u32 index, u32 addr, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_LLP_OFFSET;
	reg_dma_ch0_llp0_t ch_llp_reg;

	ch_llp_reg.val = addr;
	writel(ch_llp_reg.val, reg);
}

/**
 * csp_dma_set_channel_int_en - set channel interrupt enable
 */
void csp_dma_set_channel_int_en(u32 index, u32 half_blk, u32 tran,
					u32 blk, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_en_t ch_int_en_reg;

	ch_int_en_reg.val = 0;
	ch_int_en_reg.bits.blk = blk;
	ch_int_en_reg.bits.tran = tran;
	ch_int_en_reg.bits.half_blk = half_blk;
	ch_int_en_reg.bits.width_err = CH_INT_ENABLE;
	ch_int_en_reg.bits.desc_nalig = CH_INT_ENABLE;
	ch_int_en_reg.bits.len_nalig = CH_INT_ENABLE;
	ch_int_en_reg.bits.addr_nalig = CH_INT_ENABLE;
	ch_int_en_reg.bits.fifo_under = CH_INT_ENABLE;
	ch_int_en_reg.bits.fifo_over = CH_INT_ENABLE;
	ch_int_en_reg.bits.halt = CH_INT_ENABLE;
	ch_int_en_reg.bits.abort = CH_INT_DISABLE;

	writel(ch_int_en_reg.val, reg);
}

/**
 * csp_dma_clear_channel_all_int - clear channel all interrupt
 */
void csp_dma_clear_channel_all_int(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;

	writel(CH_ALL_BITS, reg);
}

/**
 * csp_dma_check_ch_comb_int_pd - check channel combine interrupt pending
 */
u32 csp_dma_is_ch_comb_int_pd(u32 index, void *base)
{
	void *reg = base + DMA_COMB_INT_PD_OFFSET;
	reg_dma_comb_int_pd_t comb_int_pd_reg;
	u32 value = 0;

	comb_int_pd_reg.val = readl(reg);
	switch (index) {
	case 0:
		value = comb_int_pd_reg.bits.ch0_int_pd;
		break;
	case 1:
		value = comb_int_pd_reg.bits.ch1_int_pd;
		break;
	case 2:
		value = comb_int_pd_reg.bits.ch2_int_pd;
		break;
	case 3:
		value = comb_int_pd_reg.bits.ch3_int_pd;
		break;
	default:
		break;
	}

	return value;
}

/**
 * csp_dma_is_ch_tran_int_pd - check channel trans interrupt pending
 */
u32 csp_dma_is_ch_tran_int_pd(u32 index, void *base)
{
	void *reg_pd = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	void *reg_en = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	reg_dma_ch0_int_en_t ch_int_en_reg;
	u32 value;

	ch_int_en_reg.val = readl(reg_en);
	ch_int_pd_reg.val = readl(reg_pd);
	value = ch_int_pd_reg.bits.tran & ch_int_en_reg.bits.tran;
	return value;
}

/**
 * csp_dma_is_ch_blk_int_pd - check channel blk interrupt pending
 */
u32 csp_dma_is_ch_blk_int_pd(u32 index, void *base)
{
	void *reg_pd = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	void *reg_en = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	reg_dma_ch0_int_en_t ch_int_en_reg;
	u32 value;

	ch_int_en_reg.val = readl(reg_en);
	ch_int_pd_reg.val = readl(reg_pd);
	value = ch_int_pd_reg.bits.blk & ch_int_en_reg.bits.blk;
	return value;
}

/**
 * csp_dma_is_ch_hblk_int_pd - check channel blk interrupt pending
 */
u32 csp_dma_is_ch_hblk_int_pd(u32 index, void *base)
{
	void *reg_pd = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	void *reg_en = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	reg_dma_ch0_int_en_t ch_int_en_reg;
	u32 value;

	ch_int_en_reg.val = readl(reg_en);
	ch_int_pd_reg.val = readl(reg_pd);
	value = ch_int_pd_reg.bits.half_blk & ch_int_en_reg.bits.half_blk;
	return value;
}

/**
 * csp_dma_is_ch_blk_int_pd - check channel err interrupt pending
 */
u32 csp_dma_is_ch_err_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	u32 value;

	ch_int_pd_reg.val = 0;
	ch_int_pd_reg.val = readl(reg);

	value = (ch_int_pd_reg.bits.width_err |
		ch_int_pd_reg.bits.desc_nalig |
		ch_int_pd_reg.bits.len_nalig |
		ch_int_pd_reg.bits.addr_nalig |
		ch_int_pd_reg.bits.fifo_under |
		ch_int_pd_reg.bits.fifo_over);
	return value;
}

/**
 * csp_dma_is_ch_abort_int_pd - check channel abort interrupt pending
 */
u32 csp_dma_is_ch_abort_int_pd(u32 index, void *base)
{
	void *reg_pd = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	void *reg_en = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	reg_dma_ch0_int_en_t ch_int_en_reg;
	u32 value;

	ch_int_en_reg.val = readl(reg_en);
	ch_int_pd_reg.val = readl(reg_pd);
	value = ch_int_pd_reg.bits.abort & ch_int_en_reg.bits.abort;
	return value;
}

/**
 * csp_dma_is_ch_halt_int_pd - check channel halt interrupt pending
 */
u32 csp_dma_is_ch_halt_int_pd(u32 index, void *base)
{
	void *reg_pd = base + index * DMA_CHANNEL_OFFSET + DMA_INT_PD_OFFSET;
	void *reg_en = base + index * DMA_CHANNEL_OFFSET + DMA_INT_EN_OFFSET;
	reg_dma_ch0_int_pd_t ch_int_pd_reg;
	reg_dma_ch0_int_en_t ch_int_en_reg;
	u32 value;

	ch_int_en_reg.val = readl(reg_en);
	ch_int_pd_reg.val = readl(reg_pd);
	value = ch_int_pd_reg.bits.halt & ch_int_en_reg.bits.halt;
	return value;
}

/**
 * csp_dma_clear_ch_tran_int_pd - clear channel trans interrupt pending
 */
void csp_dma_clear_ch_tran_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);
	ch_int_clr_reg.bits.tran = CH_CLEAR_PENDING;
	writel(ch_int_clr_reg.val, reg);
}

/**
 * csp_dma_clear_ch_blk_int_pd - clear channel blk interrupt pending
 */
void csp_dma_clear_ch_blk_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);
	ch_int_clr_reg.bits.blk = CH_CLEAR_PENDING;
	writel(ch_int_clr_reg.val, reg);
}

/**
 * csp_dma_clear_ch_hblk_int_pd - clear channel half blk interrupt pending
 */
void csp_dma_clear_ch_hblk_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);
	ch_int_clr_reg.bits.half_blk = CH_CLEAR_PENDING;
	writel(ch_int_clr_reg.val, reg);
}

/**
 * csp_dma_clear_ch_err_int_pd - clear channel err interrupt pending
 */
void csp_dma_clear_ch_err_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);

	ch_int_clr_reg.bits.width_err = CH_CLEAR_PENDING;
	ch_int_clr_reg.bits.desc_nalig = CH_CLEAR_PENDING;
	ch_int_clr_reg.bits.len_nalig = CH_CLEAR_PENDING;
	ch_int_clr_reg.bits.addr_nalig = CH_CLEAR_PENDING;
	ch_int_clr_reg.bits.fifo_under = CH_CLEAR_PENDING;
	ch_int_clr_reg.bits.fifo_over = CH_CLEAR_PENDING;

	writel(ch_int_clr_reg.val, reg);
}

/**
 * csp_dma_clear_ch_abort_int_pd - clear channel abort interrupt pending
 */
void csp_dma_clear_ch_abort_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);
	ch_int_clr_reg.bits.abort = CH_CLEAR_PENDING;

	writel(ch_int_clr_reg.val, reg);
}

/**
 * csp_dma_clear_ch_abort_int_pd - clear channel abort interrupt pending
 */
void csp_dma_clear_ch_halt_int_pd(u32 index, void *base)
{
	void *reg = base + index * DMA_CHANNEL_OFFSET + DMA_INT_CLR_OFFSET;
	reg_dma_ch0_int_clr_t ch_int_clr_reg;

	ch_int_clr_reg.val = readl(reg);
	ch_int_clr_reg.bits.halt = CH_CLEAR_PENDING;

	writel(ch_int_clr_reg.val, reg);
}
